﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Reflection;
using SMT.SagePos.DataAccess;
using System.Data.Linq;
using System.ComponentModel;
using System.Linq.Expressions;

namespace SMT.SagePos.BusinessObjects
{
    //Precede Tables with this tag to identify what linq class they correlate to.
    [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
    sealed class LinqClassAttribute : Attribute
    {
        readonly string linqClass;
        
        //linq Table refrers to the plural form of the table name which is the Linq.Table<> object that is a property of the DataContext
        public LinqClassAttribute(string linqClass) { this.linqClass = linqClass; }
        public string LinqClass { get { return linqClass; } }
    }

    //Precede Properties with this tag to identify what linq property they correlate to.
    [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
    sealed class LinqPropertyAttribute : Attribute
    {
        readonly string linqProperty;
        public LinqPropertyAttribute(string linqProperty) { this.linqProperty = linqProperty; }
        public string LinqProperty { get { return linqProperty; } }
    }
    
    //Calls methods to get data between database and linq classes, and between linq and business objects classes
    //Handles moving data between linq classes and business objects
    public class BusinessObjectBase
    {
        protected internal dynamic _item; //references a linq object (Customer, Vendor, etc.)

        //return a string containing the name of the linq Class the derived class represents
        private string LinqClassName { get; set; }

        public BusinessObjectBase(object linqItem)
        {
            _item = linqItem;

            LinqClassName = ((LinqClassAttribute)
                            (this.GetType()
                            .GetCustomAttributes(typeof(LinqClassAttribute), false)
                            .Single()))
                            .LinqClass;

            LoadProperties();
        }

        private void SaveProperties() //move data from business object to linq class
        {
            MoveProperties((p, pi) =>
            {
                object theValue = pi.GetValue(this, null);
                pi.SetValue(_item, theValue, null);
            });
        }
        private void LoadProperties() //move data from linq class to business object
        {
            MoveProperties((p, pi) =>
            {
                object theValue = p.GetValue(_item, null);
                pi.SetValue(this, theValue, null);
            });
        }

        //Called from SaveProperties or LoadProperties with an Action (saving or loading) to perform.
        private void MoveProperties(Action<PropertyInfo, PropertyInfo> action)
        {
            //Get all properties defined in the derived type
            PropertyInfo[] pis = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);

            foreach (PropertyInfo pi in pis)
            {
                //get the LinqPropertyAttribute for this property
                LinqPropertyAttribute fna = (LinqPropertyAttribute)(pi.GetCustomAttributes(typeof(LinqPropertyAttribute), false)).SingleOrDefault();

                //get the type for the linq class the derived class correlates to
                Type tableType = Assembly.GetAssembly(typeof(POSDataContext)).GetType("SMT.SagePos.DataAccess." + LinqClassName);

                //Get the property in the linq class that correlates to pi
                PropertyInfo p = tableType.GetProperty(fna.LinqProperty);

                action(p, pi); //loads or saves props between business object and linq class, depending on what action parameter sent in was
            }

        }

        public delegate void ValidateEventHandler();
        public event ValidateEventHandler Validate;

        protected virtual void onValidate()
        {
            if (Validate != null) Validate();
        }

        public virtual int? id
        {
            get { throw new NotImplementedException("Classes deriving from BusinessObjectBase must override the id property"); }
            protected set { }
        }
    }

    public class BusinessObjectCollectionBase<LinqClass>: IEnumerable<BusinessObjectBase>, IListSource
        where LinqClass: class
    {
        protected POSDataContext pdc = new POSDataContext();
        protected dynamic _table; //the linq table this collection gets its data from

        protected List<BusinessObjectBase> _boList = new List<BusinessObjectBase>();

        private string LinqClassName { get; set; }

        public BusinessObjectCollectionBase()
        {
            LinqClassName = ((LinqClassAttribute)
                            this.GetType()
                            .GetCustomAttributes(typeof(LinqClassAttribute), false)
                            .Single())
                            .LinqClass;

            PropertyInfo tableInfo = pdc.GetType().GetProperties().Single(o => o.Name == LinqClassName);
            _table = tableInfo.GetValue(pdc, null);
        }

        protected BusinessObjectCollectionBase(Func<BusinessObjectBase, bool> condition) : this()
        {
            Load(condition);
        }

        public BusinessObjectBase this[int index]
        {
            get
            {
                return _boList[index];
            }
        }

        public int Count()
        {
            return _boList.Count();
        }

        public int CountAll()
        {
            Table<LinqClass> items = typeof(POSDataContext).GetProperty("Customers").GetValue(pdc, null) as Table<LinqClass>;
            return items.Count();
        }

        public virtual object GetItem(dynamic item)
        {
            throw new NotImplementedException("Classes deriving from BusinessObjectCollectionBase must implement GetItem()");
            //override this method in a derived class
            //create a new object from a class that inherits from BusinessObjectBase
            //add it to _boList
        }


        public void LoadAll()
        {
            foreach (var item in _table) 
                this._boList.Add(GetItem(item));
        }

        protected virtual void Load(dynamic condition)
        { 
            var items = _table as IEnumerable<object>;

            foreach (var item in _table)
            {
                var busObject = GetItem(item);
                if (condition(busObject)) _boList.Add(busObject);
            }
        }

        //Use for paging data to avoid loading everything at once
        protected virtual void LoadPage<BOC>(int lowerBoundary, int numRows, string propertyToOrderBy)
            where BOC : BusinessObjectBase //Type of item used by derived class calling this method
        {
            _boList.Clear();
            
            //Get type of business object, and property to order by
            Type boItemType = typeof(BOC);    
            PropertyInfo piBoOrderBy = boItemType.GetProperty(propertyToOrderBy); 
            
            //Get attribute that contains name of linq property to order by
            LinqPropertyAttribute attr = 
                piBoOrderBy.GetCustomAttributes(typeof(LinqPropertyAttribute), false).Single() as LinqPropertyAttribute;

            string linqPropToOrderBy = attr.LinqProperty;

            //Get type of linq class, and property to order by
            Type linqItemType = typeof(LinqClass);
            PropertyInfo piLinqOrderBy = linqItemType.GetProperty(linqPropToOrderBy);

            //Create function to use in OrderBy expression
            Func<LinqClass, int?> exp = o => (int?)piLinqOrderBy.GetValue(o, null);

            Table<LinqClass> table = _table;
            foreach (dynamic item in table.OrderBy(exp).Skip(lowerBoundary).Take(numRows))
            {
                _boList.Add(GetItem(item));
            }

            
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            foreach (var bo in _boList) yield return bo;
        }

        IEnumerator<BusinessObjectBase> IEnumerable<BusinessObjectBase>.GetEnumerator()
        {
            foreach (var bo in _boList) yield return bo;
        }

        bool IListSource.ContainsListCollection
        {
            get { return false; }
        }

        IList IListSource.GetList()
        {
            return _boList;
        }
    }
}







//private void Insert()
//{            
//    // Get the table (ie. pdc.Customers, pdc.Vendors, or whatever table the class represents)
//    Type pdcType = pdc.GetType();
//    object table = pdcType.InvokeMember(LinqTableName, BindingFlags.GetProperty, null, pdc, null);

//    // Invoke InsertOnSubmit so the new record will be saved when SubmitChanged is called
//    Type pdcTableType = table.GetType();
//    object[] parameters = {_item};
//    pdcTableType.InvokeMember("InsertOnSubmit", BindingFlags.InvokeMethod, null, table, parameters);
//}